//== GAIA ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"

//#include "/home/odin/myhome/downloads/llvm/3.1-release/gnubuild/include/llvm/Support/DataTypes.h"
//#include "C:/Users/odin/Downloads/llvm/3.1-release/xllvm-3.1-build/include/llvm/Support/DataTypes.h"
#include "llvm/Support/DataTypes.h"
#include "llvm/DerivedTypes.h"
#include "llvm/Module.h"
//#include "llvm/ModuleProvider.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/JIT.h"
//#include "llvm/ExecutionEngine/Interpreter.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/LLVMContext.h"
//#include "llvm/Type.h"
//#include "llvm/Target/TargetData.h"
//#include "llvm/Transforms/Scalar.h"
#include "llvm/IRBuilder.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Analysis/Verifier.h" //for llvm::verifyFunction

#include <iostream>
#include <stdio.h>

//static llvm::Module *M;
//static llvm::ExecutionEngine *EE;

llvm::Module *M;
llvm::ExecutionEngine* EE;
llvm::IRBuilder<>* builder;

llvm::Function* g;

llvm::Instruction* inst;

llvm::Function* fprintHelloWorld;
llvm::Function* fmodifyInline;

extern "C" void modifyInline() {
    printf("Modify inline\n");
    builder->SetInsertPoint(inst);
    builder->CreateCall(fprintHelloWorld);
    EE->recompileAndRelinkFunction(g);
    M->dump();
}

extern "C" void printHelloWorld() {
    printf("Hello world, main code\n");
}

int main(int argc, char* argv[])
{
    llvm::InitializeNativeTarget();
    llvm::LLVMContext &Context = llvm::getGlobalContext();
    // Make the module, which holds all the code.
    M = new llvm::Module("modula", Context);
    // create ee
    EE = llvm::ExecutionEngine::create(M, false);
    // create builder
    builder = new llvm::IRBuilder<>(llvm::getGlobalContext());
    
    // set up the function hook
    fprintHelloWorld = llvm::cast<llvm::Function>(M->getOrInsertFunction("funHelloWorld", llvm::Type::getVoidTy(llvm::getGlobalContext()), NULL));
    EE->addGlobalMapping(fprintHelloWorld, (void*)(&printHelloWorld));
    
    fmodifyInline = llvm::cast<llvm::Function>(M->getOrInsertFunction("modifyInline", llvm::Type::getVoidTy(llvm::getGlobalContext()), NULL));
    EE->addGlobalMapping(fmodifyInline, (void*)(&modifyInline));
    
    // set up the native function
    g = llvm::cast<llvm::Function>(M->getOrInsertFunction("g", llvm::Type::getVoidTy(llvm::getGlobalContext()), NULL));
    llvm::BasicBlock *BB = llvm::BasicBlock::Create(llvm::getGlobalContext(), "entry", g);
    builder->SetInsertPoint(BB);
    //std::vector<llvm::GenericValue> noargs;
    //std::vector<llvm::Value*> noargs;
    //builder->CreateCall(fmodifyInline, std::vector<llvm::Value*>(), "calltmp");
    builder->CreateCall(fmodifyInline);
    inst = builder->CreateRetVoid();
    llvm::verifyFunction(*g);
    
    // run the function
    std::vector<llvm::GenericValue> noargs;
    EE->runFunction(g, noargs);
    EE->runFunction(g, noargs);
    EE->runFunction(g, noargs);
    
    delete EE;
    delete builder;
    //delete M; //do we need this? I think LLVM takes care of it
    //llvm::llvm_shutdown();
    return 0;
}
